#include <retro.h>
#include "retro.creator.h"

using namespace Retro::Creator::Toolbox;
using namespace SPLib::Geom;

#define INDEXMARGIN     50
#define CONTENTSPACE    2
#define DRAGARROWSIZE   8.0f
#define PREVIEWSIZE     16

SpriteList::SpriteList() :
  mFont(_T("Arial"), 10, Gdip::FontStyleRegular)
{
  currSprite  = NULL;
  mEditor     = NULL;
  overedIndex = -1;
  lastIndex   = -1;
  selectMode  = Editor::SelectModeNone;

  setItemHeight(PREVIEWSIZE + CONTENTSPACE * 2);
}

VOID    SpriteList::paintItem(
  Gdip::Graphics&     g,
  const Gdip::Rect&   bounds,
  INT                 index
  )
{
  if (!mEditor) return;

  Data::Texture* wTexture;

  Gdip::Rect rectIndex(
    bounds.X - 1,
    bounds.Y,
    INDEXMARGIN,
    bounds.Height);
  Gdip::Rect rectPreview(
    INDEXMARGIN + CONTENTSPACE,
    bounds.Y + (bounds.Height - PREVIEWSIZE) / 2,
    PREVIEWSIZE,
    PREVIEWSIZE);

  seekSprite(index);

  g.SetSmoothingMode(Gdip::SmoothingModeNone);
  g.SetPixelOffsetMode(Gdip::PixelOffsetModeHalf);

  g.FillRectangle(&Gdip::SolidBrush((index % 2) ? 0xFFEEEEEE : 0xFFFFFFFF),
    Gdip::Rect(bounds.X, bounds.Y, bounds.Width - 1, bounds.Height));

  if (mEditor->isSpriteSelected(currSprite))
  {
    g.FillRectangle(
      &Gdip::SolidBrush(Control::getTheme().frameSurface & 0x7FFFFFFF),
      rectIndex);
  }
  else if (mEditor->isSpriteLocked(currSprite))
  {
    g.FillRectangle(
      &Gdip::SolidBrush(0xFF666666),
      rectIndex);
  }
  else if (overedIndex == index)
  {
    g.FillRectangle(
      &Gdip::SolidBrush(Control::getTheme().frameSurface & 0x7FFFFFFF),
      rectIndex);
  }
  else
  {
    g.DrawLine(&Gdip::Pen((index % 2) ? 0xFFFFFFFF : 0xFFEEEEEE, 2),
      INDEXMARGIN, bounds.Y, INDEXMARGIN, bounds.Y + bounds.Height);
  }

  if (overedIndex == index)
  {
    g.FillRectangle(
      &Gdip::SolidBrush(Control::getTheme().frameSurface),
      INDEXMARGIN, bounds.Y, bounds.Width - INDEXMARGIN, bounds.Height);
  }

  g.SetPixelOffsetMode(Gdip::PixelOffsetModeNone);

  /*if (mEditor->isSpriteHidden(currSprite))
  {
    Gdip::Pen markPen(0xCC000000, 2);
    Gdip::Rect mark = rectIndex;

    mark.Inflate(-4, -4);

    g.DrawLine(&markPen,
      mark.X,
      mark.Y,
      mark.X + mark.Width,
      mark.Y + mark.Height);

    g.DrawLine(&markPen,
      mark.X + mark.Width,
      mark.Y,
      mark.X,
      mark.Y + mark.Height);
  }*/

  if (currSprite->flags & SPRITEDATA_GROUP)
  {
    Gdip::StringFormat format;
    TCHAR szChildCount[100];

    format.SetLineAlignment(Gdip::StringAlignmentCenter);
    format.SetAlignment(Gdip::StringAlignmentCenter);

    _itot(
      Device::Display::GetSpriteChildCount(currSprite, FALSE),
      szChildCount,
      10);

    g.DrawString(
      szChildCount, -1,
      &Gdip::Font(_T("Arial"), 14),
      Geom::rectToFloat(rectPreview),
      &format,
      &Gdip::SolidBrush(0xFF000000));
  }
  else switch (currSprite->type)
  {
  case Data::SpriteTexture:
    if (wTexture = (Data::Texture*)
      Frame::get()->getProject().findResource(
      Data::ResourceTexture, currSprite->id))
    {
      g.SetInterpolationMode(Gdip::InterpolationModeNearestNeighbor);
      g.SetCompositingQuality(Gdip::CompositingQualityAssumeLinear);
      g.SetPixelOffsetMode(Gdip::PixelOffsetModeNone);

      g.DrawImage(
        wTexture->getBitmap(),
        rectPreview,
        currSprite->source.x,
        currSprite->source.y,
        currSprite->source.cx,
        currSprite->source.cy,
        Gdip::UnitPixel);
    }
    break;
  case Data::SpriteRect:
    g.FillRectangle(&Gdip::SolidBrush(currSprite->color[0]), rectPreview);
    break;
  }

  {
    Gdip::StringFormat format;

    format.SetLineAlignment(Gdip::StringAlignmentCenter);
    format.SetAlignment(Gdip::StringAlignmentFar);

    TCHAR szIndex[100];
    _stprintf(szIndex, _T("%d"), index + 1);

    rectIndex.Inflate(-2, 0);

    g.DrawString(szIndex, -1,
      &Gdip::Font(_T("Arial"), 10, Gdip::FontStyleBold),
      Geom::rectToFloat(rectIndex),
      &format,
      &Gdip::SolidBrush(
      mEditor->isSpriteSelected(currSprite) || overedIndex == index ||
      mEditor->isSpriteLocked(currSprite) ?
      Control::getTheme().textLight : Control::getTheme().textDark));
  }

  {
    TCHAR text[RETRO_MAXTEXT];
    Gdip::StringFormat format(Gdip::StringFormatFlagsNoWrap);
    Gdip::RectF textArea(
      (Gdip::REAL)INDEXMARGIN + CONTENTSPACE * 2 + PREVIEWSIZE,
      (Gdip::REAL)bounds.Y,
      (Gdip::REAL)0,
      (Gdip::REAL)bounds.Height);

    PCTSTR wTileType    = _T("");
    PCTSTR wTilePrefix  = _T("");

    textArea.Width = bounds.Width - textArea.X - CONTENTSPACE;

    format.SetLineAlignment(Gdip::StringAlignmentCenter);

    if (currSprite->flags & SPRITEDATA_TILE)
      wTilePrefix = _T("Tiled ");

    if (currSprite->flags & SPRITEDATA_GROUP)
    {
      wTileType = _T("Group");
    }
    else switch (currSprite->type)
    {
    case Data::SpriteTexture:
      wTileType = _T("Texture");
      break;
    case Data::SpriteRect:
      wTileType = _T("Rect");
      break;
    }

    _stprintf(text, _T("%s%s"), wTilePrefix, wTileType);

    g.DrawString(
      text, -1, &mFont, textArea, &format,
      &Gdip::SolidBrush(overedIndex == index ?
      Control::getTheme().textLight : Control::getTheme().textDark));

    format.SetAlignment(Gdip::StringAlignmentFar);

    _stprintf(
      text, _T("[%d x %d]"),
      currSprite->bounds.cx,
      currSprite->bounds.cy);

    g.DrawString(
      text, -1, &mFont, textArea, &format,
      &Gdip::SolidBrush(overedIndex == index ?
      Control::getTheme().textLight : Control::getTheme().textDark));
  }
}

VOID    SpriteList::paintForeground(
  Gdip::Graphics&     g,
  const Gdip::Rect&   bounds
  )
{
  if (selectMode == Editor::SelectModeDrag)
  {
    Gdip::PointF arrow[POINT_COUNT_TRIANGLE];
    Gdip::SolidBrush brush(Control::getTheme().borderDark);

    INT dragY = (dragIndex == -1 ? 0 :
      (dragIndex + 1) * getItemHeight()) - scroll.pos.y;

    g.DrawLine(&Gdip::Pen(Control::getTheme().borderDark, 2),
      0, dragY, (INT)bounds.Width, dragY);

    GetRightTriangle(arrow, Gdip::RectF(
      0,
      dragY - DRAGARROWSIZE / 2,
      DRAGARROWSIZE,
      DRAGARROWSIZE));

    g.FillPolygon(&brush, arrow, POINT_COUNT_TRIANGLE);

    GetLeftTriangle(arrow, Gdip::RectF(
      bounds.Width - DRAGARROWSIZE,
      dragY - DRAGARROWSIZE / 2,
      DRAGARROWSIZE,
      DRAGARROWSIZE));

    g.FillPolygon(&brush, arrow, POINT_COUNT_TRIANGLE);
  }
}

VOID    SpriteList::updateList()
{
  if (!mEditor)
  {
    setItemCount(0);
    return;
  }

  Data::SpriteData* data = mEditor->getFirstSprite();
  INT count = 0;

  while (data)
  {
    ++count;
    data = mEditor->getNextSprite(data);
  }

  selectMode  = Editor::SelectModeNone;
  currSprite  = NULL;
  overedIndex = -1;
  lastIndex   = -1;
  setItemCount(count);
}

VOID    SpriteList::seekSprite(INT index)
{
  if (!mEditor) return;

  if (index == 0 || !currSprite)
  {
    currSprite = mEditor->getFirstSprite();
    spriteIndex = 0;
  }

  while (index < spriteIndex && currSprite)
  {
    currSprite = mEditor->getPrevSprite(currSprite);
    --spriteIndex;
  }

  while (index > spriteIndex && currSprite)
  {
    currSprite = mEditor->getNextSprite(currSprite);
    ++spriteIndex;
  }
}

VOID    SpriteList::onMouseDown(Event::MouseEvent& iEvent)
{
  if (!mEditor) return;

  focus();

  if (overedIndex == -1)
    return;

  if (iEvent.mkButton & MK_LBUTTON)
  {
    if (GetKeyState(VK_SHIFT) >> 1 && lastIndex != -1)
    {
      seekSprite(lastIndex);

      while (lastIndex != overedIndex)
      {
        if (lastIndex < overedIndex)
        {
          currSprite = mEditor->getNextSprite(currSprite);
          ++lastIndex;
        }
        else
        {
          currSprite = mEditor->getPrevSprite(currSprite);
          --lastIndex;
        }

        mEditor->selectSprite(currSprite);
      }
    }
    else if (GetKeyState(VK_CONTROL) >> 1)
    {
      seekSprite(overedIndex);

      if (mEditor->isSpriteSelected(currSprite))
        mEditor->deselectSprite(currSprite);
      else
        mEditor->selectSprite(currSprite);
    }
    else
    {
      seekSprite(overedIndex);

      if (!mEditor->isSpriteSelected(currSprite))
      {
        mEditor->deselectAll();
        mEditor->selectSprite(currSprite);
      }

      dragIndex = -1;
      selectMode = Editor::SelectModeSelect;
    }
  }

  lastIndex = overedIndex;
}

VOID    SpriteList::onMouseMove(Event::MouseEvent& iEvent)
{
  if (!mEditor) return;

  scrollToPoint(iEvent.point, scroll);
  scrollPoint(iEvent.point, scroll);

  if (selectMode == Editor::SelectModeSelect ||
    selectMode == Editor::SelectModeDrag)
  {
    INT destIndex = (iEvent.y + getItemHeight() / 2) / getItemHeight() - 1;

    selectMode = Editor::SelectModeDrag;

    if (destIndex != dragIndex)
    {
      invalidateItem(dragIndex - 1);
      invalidateItem(dragIndex);
      invalidateItem(dragIndex + 1);
      dragIndex = destIndex;
      invalidateItem(dragIndex - 1);
      invalidateItem(dragIndex);
      invalidateItem(dragIndex + 1);
    }
  }
  else
  {
    INT selIndex = iEvent.y / getItemHeight();

    if (selIndex >= getItemCount()) selIndex = -1;

    if (selIndex != overedIndex)
    {
      invalidateItem(overedIndex);
      overedIndex = selIndex;
      invalidateItem(overedIndex);
    }
  }
}

VOID    SpriteList::onMouseLeave()
{
  if (overedIndex != -1)
  {
    overedIndex = -1;
    invalidate(FALSE);
  }
}

VOID    SpriteList::onMouseUp(Event::MouseEvent& iEvent)
{
  if (!mEditor) return;

  if (selectMode == Editor::SelectModeDrag)
  {
    Data::SpriteData* target;

    if (dragIndex >= 0)
    {
      seekSprite(dragIndex);
      target = currSprite;
    }
    else
    {
      target = NULL;
    }

    if (lastIndex != dragIndex &&
      dragIndex != lastIndex - 1 &&
      !mEditor->isSpriteSelected(target))
    {
      Data::SpriteData* sprite = mEditor->getFirstSprite();

      while (sprite) {
        Data::SpriteData* next = mEditor->getNextSprite(sprite);

        if (mEditor->isSpriteSelected(sprite))
          mEditor->insertSprite(sprite, target);

        sprite = next;
      }

      scrollToPoint(Point::get(0, (dragIndex + 1) * getItemHeight()), scroll);
    }

    invalidate(FALSE);
  }

  selectMode = Editor::SelectModeNone;
}

VOID    SpriteList::onKeyDown(Event::KeyEvent& iEvent)
{
  if (!mEditor) return;

  if (GetKeyState(VK_CONTROL) >> 1)
  {
    if (GetKeyState(VK_SHIFT) >> 1)
    {
      switch (iEvent.vkCode)
      {
      case 'L':
        mEditor->unlockSelection();
        break;
      case 'H':
        mEditor->showSelection();
        break;
      }
    }
    else
    {
      switch (iEvent.vkCode)
      {
      case 'A':
        mEditor->selectAll();
        break;
      case 'L':
        mEditor->lockSelection();
        break;
      case 'H':
        mEditor->hideSelection();
        break;
      }
    }
  }
  else if (GetKeyState(VK_SHIFT) >> 1)
  {
    switch (iEvent.vkCode)
    {
    case VK_LEFT:
      mEditor->alterSelection(Bounds::get(-5, 0, 0, 0));
      break;
    case VK_RIGHT:
      mEditor->alterSelection(Bounds::get(5, 0, 0, 0));
      break;
    case VK_UP:
      mEditor->alterSelection(Bounds::get(0, -5, 0, 0));
      break;
    case VK_DOWN:
      mEditor->alterSelection(Bounds::get(0, 5, 0, 0));
      break;
    }
  }
  else
  {
    switch (iEvent.vkCode)
    {
    case VK_DELETE:
      mEditor->deleteSelection();
      break;
    case VK_LEFT:
      mEditor->alterSelection(Bounds::get(-1, 0, 0, 0));
      break;
    case VK_RIGHT:
      mEditor->alterSelection(Bounds::get(1, 0, 0, 0));
      break;
    case VK_UP:
      mEditor->alterSelection(Bounds::get(0, -1, 0, 0));
      break;
    case VK_DOWN:
      mEditor->alterSelection(Bounds::get(0, 1, 0, 0));
      break;
    }
  }
}
